using System;
using System.Linq;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Collections.Specialized;
using System.Collections.Generic;

namespace ReactiveLang.Collections
{
    using Util;

    partial class EnumerableExpression
    {
        internal static IEnumerableExpression<T> OnlyMaterializedItems<T>(this IEnumerableExpression<T> source)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");

            return Create<T>(p =>
            {
                var items = new List<Expression.LatestValue<T>>();
                Func<Expression.LatestValue<T>, int> indexFor = item => items.TakeWhile(x => x != item).Count(x => x.HasValue);
                var handle = source.Activate(
                    onItem: (_item, index) =>
                    {
                        if (index < 0 || index > items.Count)
                            index = items.Count;

                        var item = _item.Latest(p.OnException);
                        items.Insert(index, item);
                        var itemHandle = Disposable.Empty;
                        if (item.HasValue)
                            itemHandle = p.OnItem(item, indexFor(item));
                        else
                        {
                            itemHandle = item.ActivateDerived(p, x =>
                            {
                                itemHandle.Dispose();
                                itemHandle = p.OnItem(item, indexFor(item));
                            });
                        }
                        return () => itemHandle.Dispose();
                    },
                    onCleared: delegate
                    {
                        foreach (var i in items)
                            i.Dispose();
                        items.Clear();
                        p.OnCleared();
                    },
                    onException: p.OnException
                );
                return delegate
                {
                    foreach(var i in items)
                        i.Dispose();
                    items.Clear();
                    handle.Dispose();
                };
            });
        }

        public static ReadOnlyObservableCollection<T> ToObservableCollection<T>(this IEnumerableExpression<T> source)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            IDisposable handle;
            return source.ToObservableCollection(out handle);
        }
        public static ReadOnlyObservableCollection<T> ToObservableCollection<T>(this IEnumerableExpression<T> source, out IDisposable handle)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Ensures(Contract.ValueAtReturn(out handle) != null);

            var collection = new ObservableCollection<T>();
            var items = new List<Expression.LatestValue<T>>();
            handle = source.OnlyMaterializedItems().Activate(
                onItem: (_item, i) =>
                {
                    Contract.Assert(i >= 0 && i <= collection.Count);
                    var item = (Expression.LatestValue<T>)_item;
                    Contract.Assume(item.HasValue);
                    collection.Insert(i, item.Value);
                    item.Activate(x => collection[items.IndexOf(item)] = x);

                    return delegate
                    {
                        Contract.Assume(item.HasValue);
                        i = items.IndexOf(item);
                        Contract.Assert(Equals(collection[i], item.Value));
                        item.Dispose();
                        items.RemoveAt(i);
                        collection.RemoveAt(i);
                    };
                },
                onCleared: delegate
                {
                    foreach(var i in items)
                        i.Dispose();
                    items.Clear();
                    collection.Clear();
                }
            );
            handle = handle.Concat(delegate
            {
                foreach (var i in items)
                    i.Dispose();
            });
            return new ReadOnlyObservableCollection<T>(collection);
        }
    }
}